<?php

/**
 * The Session class creates sessions or cookies and stores the encrypted data.
 * 
 * @author Cesar Augustus Silva <cesaraugustussilva@linuxmail.org>
 * @copyright Copyright (c) 2013, Cesar Augustus Silva
 * @license http://www.gnu.org/licenses/gpl.html GNU GPL v3.0
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <­http://www.gnu.org/licenses/>.
 */
class Session {

    /** @var string The name of the session or cookie to be created */
    protected $_name;
    
    /** @var array The values that are stored in the session or cookie */
    protected $_value;
    
    /** @var int The time for the cookie expires */
    protected $_expire;
    
    /** @var string The domain for which the cookie is available */
    protected $_domain;
    
    /** @var string The path on the server where the cookie is available */
    protected $_path;
    
    /** @var string The key Session */
    protected $_key;
    
    /** @var boolean Defines whether to create sessions or cookies */
    protected $_persistent;
    
    /**
     * The constructor magic method of the Session class
     * 
     * @param string $name The name of the Session
     * @param boolean $persistent Defines if is used cookie or session<br>
     *                            <b>TRUE</b> on cookie or <b>FALSE</b> on session
     * @return void No value is returned
     */
    public function __construct($name, $persistent = false) {
        session_start();
        
        $this->_name = md5($name);
        $this->_key = _SESSION_KEY_;
        $this->_expire = (time() + ((int)_COOKIE_LIFETIME_ > 0 ? (int)_COOKIE_LIFETIME_ : 1728000));
        $this->_path = __BASE_URI__._COOKIE_PATH_;
        $this->_domain = _COOKIE_DOMAIN_;
        
        if ($persistent)
            $this->_persistent = true;
        else {
            if (isset($_COOKIE[$this->_name]))
                $this->_persistent = true;
            else
                $this->_persistent = false;
        }
        
        if (($this->_persistent && isset($_COOKIE[$this->_name])) || (!$this->_persistent && isset($_SESSION[$this->_name]))) {
            if ($this->_persistent)
                $value = $_COOKIE[$this->_name];
            else
                $value = $_SESSION[$this->_name];
            
            $overload = ((1 << 1) & ini_get('mbstring.func_overload')) ? 1 : 2;
            $checksum = crc32($this->_key.substr($value, 0, strrpos($value, '¤') + $overload));
            
            $vector = explode('¤', $value);
            foreach ($vector AS $val) {
                $vector2 = explode('|', base64_decode($val));
                if (count($vector2) == 2)
                     $this->_value[$vector2[0]] = $vector2[1];
            }
            
            if (isset($this->_value['checksum']))
                $this->_value['checksum'] = (int)($this->_value['checksum']);
            
            if (!isset($this->_value['checksum']) || $this->_value['checksum'] != $checksum)
                $this->destroy();
        }
    }
    
    /**
     * The magic method to get the value written the array
     * 
     * @param string $key The key of the array
     * @return mixed The value obtained in the array
     */
    public function __get($key) {
        return isset($this->_value[$key]) ? $this->_value[$key] : null;
    }
    
    /**
     * The magic method to tell if the key was initiated in array
     * 
     * @param string $key The key of the array
     * @return boolean <b>TRUE</b> if the key exists or <b>FALSE</b> if not exists
     */
    public function __isset($key) {
        return isset($this->_value[$key]);
    }
    
    /**
     * The magic method to write the value inside the array
     * 
     * @param string $key The key of the array
     * @param mixed $value The value to be written in the array
     * @return boolean <b>TRUE</b> if the key exists or <b>FALSE</b> if not exists
     */
    public function __set($key, $value) {
        if (preg_match('/¤|\|/', $key.$value))
            die('Forbidden chars in session');
        if (!isset($this->_value[$key]) || (isset($this->_value[$key]) && $this->_value[$key] != $value)) {
            $this->_value[$key] = $value;
            $this->write();
        }
    }
    
    /**
     * The magic method for destroys the key inside the array
     * 
     * @param string $key The key of the array
     * @return void No value is returned
     */
    public function __unset($key) {
        if (isset($this->_value[$key])) {
            unset($this->_value[$key]);
            $this->write();
        }
    }
    
    /**
     * Destroys the session or cookie exists or was created by the Session class
     * 
     * @return void No value is returned
     */
    public function destroy() {
        $this->_value = array();
        setcookie($this->_name, '', time() - 3600, $this->_path, $this->_domain);
        unset($_COOKIE[$this->_name]);
        //session_unset();
        //session_destroy();
        unset($_SESSION[$this->_name]);
    }
    
    /**
     * Encrypts the keys and values of the array and adds in session or cookie
     * 
     * @return void No value is returned
     */
    protected function write() {
        $value = '';
        if (isset($this->_value['checksum']))
            unset($this->_value['checksum']);
        foreach ($this->_value as $key => $val)
            $value .= base64_encode($key.'|'.$val).'¤';
        $value .= base64_encode('checksum|'.crc32($this->_key.$value));
        
        if ($this->_persistent)
            setcookie($this->_name, $value, $this->_expire, $this->_path, $this->_domain);
        else
            $_SESSION[$this->_name] = $value;
    }

}